/************************************************************************************
 * Copyright (c) 2008 William Chen.                                                 *
 *                                                                                  *
 * All rights reserved. This program and the accompanying materials are made        *
 * available under the terms of the Eclipse Public License v1.0 which accompanies   *
 * this distribution, and is available at http://www.eclipse.org/legal/epl-v10.html *
 *                                                                                  *
 * Use is subject to the terms of Eclipse Public License v1.0.                      *
 *                                                                                  *
 * Contributors:                                                                    * 
 *     William Chen - initial API and implementation.                               *
 ************************************************************************************/

package org.dyno.visual.swing.parser;

import java.awt.Component;
import java.awt.Container;
import java.awt.Frame;
import java.beans.EventSetDescriptor;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.List;

import javax.swing.JComponent;
import javax.swing.JPopupMenu;
import javax.swing.LookAndFeel;
import javax.swing.UIManager;

import org.dyno.visual.swing.base.ExtensionRegistry;
import org.dyno.visual.swing.base.ISyncUITask;
import org.dyno.visual.swing.base.JavaUtil;
import org.dyno.visual.swing.parser.spi.IFieldParser;
import org.dyno.visual.swing.parser.spi.IParser;
import org.dyno.visual.swing.parser.spi.IWidgetASTParser;
import org.dyno.visual.swing.plugin.spi.CompositeAdapter;
import org.dyno.visual.swing.plugin.spi.IConstants;
import org.dyno.visual.swing.plugin.spi.ILookAndFeelAdapter;
import org.dyno.visual.swing.plugin.spi.ISourceParser;
import org.dyno.visual.swing.plugin.spi.InvisibleAdapter;
import org.dyno.visual.swing.plugin.spi.ParserException;
import org.dyno.visual.swing.plugin.spi.WidgetAdapter;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IConfigurationElement;
import org.eclipse.core.runtime.IExtension;
import org.eclipse.core.runtime.IExtensionPoint;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.Platform;
import org.eclipse.jdt.core.Flags;
import org.eclipse.jdt.core.ICompilationUnit;
import org.eclipse.jdt.core.IField;
import org.eclipse.jdt.core.IJavaProject;
import org.eclipse.jdt.core.IMethod;
import org.eclipse.jdt.core.IType;
import org.eclipse.jdt.core.JavaModelException;
import org.eclipse.jdt.core.dom.AST;
import org.eclipse.jdt.core.dom.ASTParser;
import org.eclipse.jdt.core.dom.CompilationUnit;
import org.eclipse.jdt.core.dom.TypeDeclaration;
import org.eclipse.jdt.core.dom.rewrite.ImportRewrite;
import org.eclipse.jdt.ui.CodeStyleConfiguration;
import org.eclipse.jdt.ui.actions.OrganizeImportsAction;
import org.eclipse.text.edits.TextEdit;
import org.eclipse.ui.IEditorPart;
import org.eclipse.ui.IWorkbench;
import org.eclipse.ui.IWorkbenchPage;
import org.eclipse.ui.IWorkbenchPartSite;
import org.eclipse.ui.IWorkbenchWindow;
import org.eclipse.ui.PlatformUI;

/**
 * 
 * DefaultSourceParser
 * 
 * @version 1.0.0, 2008-7-3
 * @author William Chen
 */
@SuppressWarnings("unchecked")
class DefaultSourceParser implements ISourceParser, IConstants {
	private static final String FIELD_PARSER_EXTENSION_ID = "org.dyno.visual.swing.parser.fieldParser"; //$NON-NLS-1$
	private DefaultParserFactory factory;
	private List<IFieldParser> fieldParsers;

	DefaultSourceParser(DefaultParserFactory factory) {
		this.factory = factory;
		fieldParsers = new ArrayList<IFieldParser>();
		parseFieldParsers();
	}

	private void parseFieldParsers() {
		IExtensionPoint extensionPoint = Platform.getExtensionRegistry().getExtensionPoint(FIELD_PARSER_EXTENSION_ID);
		if (extensionPoint != null) {
			IExtension[] extensions = extensionPoint.getExtensions();
			if (extensions != null && extensions.length > 0) {
				for (int i = 0; i < extensions.length; i++) {
					parseFieldParser(extensions[i]);
				}
			}
		}
	}

	private void parseFieldParser(IExtension extension) {
		IConfigurationElement[] configs = extension.getConfigurationElements();
		if (configs != null && configs.length > 0) {
			for (int i = 0; i < configs.length; i++) {
				String name = configs[i].getName();
				if (name.equals("parser")) { //$NON-NLS-1$
					try {
						fieldParsers.add((IFieldParser) configs[i].createExecutableExtension("class")); //$NON-NLS-1$
					} catch (CoreException e) {
						ParserPlugin.getLogger().error(e);
					}
				}
			}
		}
	}

	@Override
	public WidgetAdapter parse(ICompilationUnit unit, IProgressMonitor monitor) throws ParserException {
		try {
			IType[] types = unit.getPrimary().getAllTypes();
			for (IType type : types) {
				if (type.isClass() && Flags.isPublic(type.getFlags())) {
					WidgetAdapter result = processType(unit.getPrimary(), type);
					if (result != null)
						return result;
				}
			}
		} catch (JavaModelException jme) {
			ParserPlugin.getLogger().error(jme);
			throw new ParserException(jme);
		}
		return null;
	}
	private static synchronized Object runWithLnf(LookAndFeel lnf,
			ISyncUITask task) throws Throwable {
		UIManager.setLookAndFeel(lnf);
		return task.doTask();
	}
	private WidgetAdapter processType(ICompilationUnit unit, IType type) throws ParserException {
		try {
			IJavaProject java_project = type.getJavaProject();
			String className = type.getFullyQualifiedName();
			final Class<?> beanClass = JavaUtil.getProjectClassLoader(java_project).loadClass(className);
			if (Component.class.isAssignableFrom(beanClass)) {
				String lnf = getBeanClassLnf(beanClass);
				ILookAndFeelAdapter lnfAdapter = ExtensionRegistry.getLnfAdapter(lnf);
				if (lnfAdapter != null) {
					LookAndFeel newlnf = lnfAdapter.getLookAndFeelInstance();
					Component bean = (Component) runWithLnf(newlnf, new ISyncUITask() {
						@Override
						public Object doTask() throws Throwable {
							return createBeanFromClass(beanClass);
						}
					});
					WidgetAdapter beanAdapter = ExtensionRegistry.createWidgetAdapter(bean);
					ASTParser parser = ASTParser.newParser(AST.JLS3);
					parser.setSource(unit);
					CompilationUnit cunit = (CompilationUnit) parser.createAST(null);
					parseEventListener(cunit, beanAdapter);
					initDesignedWidget(cunit, bean);
					parsePropertyValue(lnf, cunit, beanAdapter);
					beanAdapter.clearDirty();
					return beanAdapter;
				}
			} else {
				throw new ParserException("This is not a swing class!");
			}
		} catch (ParserException pe) {
			throw pe;
		} catch (Throwable e) {
			ParserPlugin.getLogger().error(e);
			throw new ParserException(e);
		}
		return null;
	}

	private Object createBeanFromClass(Class beanClass) throws Throwable {
		try {
			Constructor cons = beanClass.getConstructor(new Class[0]);
			cons.setAccessible(true);
			return cons.newInstance();
		} catch (NoSuchMethodException e) {
			try {
				Constructor cons = beanClass.getConstructor(new Class[] { Frame.class });
				cons.setAccessible(true);
				return cons.newInstance(new Frame());
			} catch (NoSuchMethodException ex) {
				try {
					Constructor cons = beanClass.getConstructor(new Class[] { String.class });
					cons.setAccessible(true);
					return cons.newInstance(new String());
				} catch (NoSuchMethodException exx) {
					return null;
				}
			}
		}
	}

	private void parsePropertyValue(String lnfClassname, CompilationUnit cunit, WidgetAdapter adapter) {
		TypeDeclaration type = (TypeDeclaration) cunit.types().get(0);
		IWidgetASTParser widgetParser = (IWidgetASTParser) adapter.getAdapter(IWidgetASTParser.class);
		widgetParser.parse(lnfClassname, type);
		if (adapter instanceof CompositeAdapter) {
			CompositeAdapter compositeAdapter = (CompositeAdapter) adapter;
			int count = compositeAdapter.getChildCount();
			for (int i = 0; i < count; i++) {
				Component child = compositeAdapter.getChild(i);
				WidgetAdapter childAdapter = WidgetAdapter.getWidgetAdapter(child);
				parsePropertyValue(lnfClassname, cunit, childAdapter);
			}
		}
	}

	private void initDesignedWidget(CompilationUnit cunit, Component bean) throws ParserException {
		Class clazz = bean.getClass();
		Field[] fields = clazz.getDeclaredFields();
		for (Field field : fields) {
			try {
				field.setAccessible(true);
				Object fieldValue = field.get(bean);
				if (fieldValue != null) {
					if (Component.class.isAssignableFrom(field.getType())) {
						parseField(cunit, bean, field);
					} else {
						for (IFieldParser parser : fieldParsers) {
							parser.parseField(cunit, bean, field);
						}
					}
				}
			} catch (ParserException e) {
				throw e;
			} catch (Exception e) {
				ParserPlugin.getLogger().error(e);
				throw new ParserException(e);
			}
		}
	}

	private void parseField(CompilationUnit cunit, Component bean, Field field) throws ParserException {
		Class clazz = bean.getClass();
		String fieldName = field.getName();
		field.setAccessible(true);
		Object fieldValue = null;
		try {
			fieldValue = field.get(bean);
		} catch (Exception e) {
			ParserPlugin.getLogger().error(e);
			throw new ParserException(e);
		}

		JComponent fieldComponent = (JComponent) fieldValue;
		WidgetAdapter adapter = ExtensionRegistry.createWidgetAdapter(fieldComponent, true);
		if (adapter.getWidget() != fieldComponent && fieldComponent instanceof JPopupMenu && adapter.getWidget() instanceof JPopupMenu) {
			JComponent jcomp = findPopupInvoker((JPopupMenu) fieldComponent, bean);
			if (jcomp != null) {
				jcomp.setComponentPopupMenu((JPopupMenu) adapter.getWidget());
			}
		}
		adapter.setName(fieldName);
		adapter.setLastName(fieldName);

		int flags = field.getModifiers();
		setAdapterFieldAccess(adapter, flags);

		String getName = NamespaceUtil.getGetMethodName(fieldName);
		Method getMethod = null;
		try {
			getMethod = clazz.getDeclaredMethod(getName);
		} catch (NoSuchMethodException nsme) {
			getName = NamespaceUtil.getGetMethodName(cunit, fieldName);
			if (getName == null)
				throw new ParserException("Method " + NamespaceUtil.getGetMethodName(fieldName) + "() is not found!\n" + "Please define it to initialize " + fieldName);
			try {
				getMethod = clazz.getDeclaredMethod(getName);
				WidgetAdapter ba = WidgetAdapter.getWidgetAdapter(fieldComponent);
				ba.setProperty("getMethodName", getName);
			} catch (NoSuchMethodException e) {
				throw new ParserException("Method " + getName + "() is not found!\n" + "Please define it to initialize " + fieldName);
			}
		}

		flags = getMethod.getModifiers();
		setAdapterGetMethodAccess(adapter, flags);
		parseEventListener(cunit, adapter);
	}

	private JComponent findPopupInvoker(JPopupMenu popup, Component bean) {
		if (bean instanceof Container) {
			if (bean instanceof JComponent) {
				JComponent jcomp = (JComponent) bean;
				if (jcomp.getComponentPopupMenu() == popup)
					return jcomp;
			}
			Container container = (Container) bean;
			int count = container.getComponentCount();
			for (int i = 0; i < count; i++) {
				Component child = container.getComponent(i);
				JComponent p = findPopupInvoker(popup, child);
				if (p != null)
					return p;
			}
		}
		return null;
	}

	private void setAdapterGetMethodAccess(WidgetAdapter adapter, int flags) {
		if (Modifier.isPrivate(flags)) {
			adapter.setGetAccess(WidgetAdapter.ACCESS_PRIVATE);
		} else if (Modifier.isProtected(flags)) {
			adapter.setGetAccess(WidgetAdapter.ACCESS_PROTECTED);
		} else if (Modifier.isPublic(flags)) {
			adapter.setGetAccess(WidgetAdapter.ACCESS_PUBLIC);
		} else {
			adapter.setGetAccess(WidgetAdapter.ACCESS_DEFAULT);
		}
	}

	private void setAdapterFieldAccess(WidgetAdapter adapter, int flags) {
		if (Modifier.isPrivate(flags)) {
			adapter.setFieldAccess(WidgetAdapter.ACCESS_PRIVATE);
		} else if (Modifier.isProtected(flags)) {
			adapter.setFieldAccess(WidgetAdapter.ACCESS_PROTECTED);
		} else if (Modifier.isPublic(flags)) {
			adapter.setFieldAccess(WidgetAdapter.ACCESS_PUBLIC);
		} else {
			adapter.setFieldAccess(WidgetAdapter.ACCESS_DEFAULT);
		}
	}

	private static String getBeanClassLnf(Class beanClass) {
		try {
			Field field = beanClass.getDeclaredField("PREFERRED_LOOK_AND_FEEL"); //$NON-NLS-1$
			if (field.getType() == String.class) {
				field.setAccessible(true);
				String lnf = (String) field.get(null);
				String className = UIManager.getCrossPlatformLookAndFeelClassName();
				if (lnf == null) {
					lnf = className;
				}
				return lnf;
			}
		} catch (NoSuchFieldException nsfe) {
		} catch (Exception e) {
			ParserPlugin.getLogger().warning(e);
		}
		return UIManager.getCrossPlatformLookAndFeelClassName();
	}

	private void parseEventListener(CompilationUnit cunit, WidgetAdapter adapter) throws ParserException {
		EventSetDescriptor[] esds = adapter.getBeanInfo().getEventSetDescriptors();
		TypeDeclaration type = (TypeDeclaration) cunit.types().get(0);
		if (esds != null && esds.length > 0) {
			for (EventSetDescriptor esd : esds) {
				factory.parseEventListener(adapter, type, esd);
			}
		}
	}

	private IType getUnitMainType(ICompilationUnit unit) {
		String unit_name = unit.getElementName();
		int dot = unit_name.lastIndexOf('.');
		if (dot != -1)
			unit_name = unit_name.substring(0, dot);
		IType type = unit.getType(unit_name);
		return type;
	}

	public static ImportRewrite createImportRewrite(ICompilationUnit unit) {
		ASTParser parser = ASTParser.newParser(AST.JLS3);
		parser.setSource(unit);
		parser.setResolveBindings(false);
		parser.setFocalPosition(0);
		CompilationUnit cu = (CompilationUnit) parser.createAST(null);
		return CodeStyleConfiguration.createImportRewrite(cu, true);
	}

	@Override
	public ICompilationUnit generate(WidgetAdapter root, IProgressMonitor monitor) {
		try {
			IParser parser = (IParser) root.getAdapter(IParser.class);
			if (parser == null)
				return null;
			ICompilationUnit unit = root.getCompilationUnit();
			ICompilationUnit copy = unit.getWorkingCopy(monitor);
			IType type = getUnitMainType(copy);
			if (type != null) {
				ImportRewrite imports = createImportRewrite(copy);
				boolean success = parser.generateCode(type, imports, monitor);
				if (!success)
					return null;
				removeRemovedComponent(root, monitor, unit, type);
				createPreferredLnf(root, monitor, type, imports);
				if (success) {
					TextEdit edit = imports.rewriteImports(monitor);
					JavaUtil.applyEdit(copy, edit, true, monitor);
				}
				if (copy.isWorkingCopy()) {
					copy.commitWorkingCopy(true, monitor);
					copy.discardWorkingCopy();
				}
				IWorkbenchPartSite site = getEditorSite();
				if (site != null) {
					OrganizeImportsAction action = new OrganizeImportsAction(site);
					action.run(unit);
				}
				type = getUnitMainType(unit);
				rename(type, root);
				if (unit.isWorkingCopy()) {
					unit.commitWorkingCopy(true, monitor);
				}
				return unit;
			} else
				return null;
		} catch (Exception e) {
			ParserPlugin.getLogger().error(e);
			return null;
		}
	}

	private void createPreferredLnf(WidgetAdapter root, IProgressMonitor monitor, IType type, ImportRewrite imports) throws JavaModelException {
		IField lnfField = type.getField("PREFERRED_LOOK_AND_FEEL"); //$NON-NLS-1$		
		String className = (String) root.getPreferredLookAndFeel();
		if (lnfField.exists()) {
			lnfField.delete(false, monitor);
			createLnfField(monitor, type, imports, className);
		} else if (!isCross(className)) {
			createLnfField(monitor, type, imports, className);
		}
	}

	private void createLnfField(IProgressMonitor monitor, IType type, ImportRewrite imports, String className) throws JavaModelException {
		String newfield = "private static final " //$NON-NLS-1$
				+ imports.addImport("java.lang.String") //$NON-NLS-1$
				+ " PREFERRED_LOOK_AND_FEEL = " //$NON-NLS-1$
				+ (className == null ? "null" : "\"" + className + "\"") //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
				+ ";\n"; //$NON-NLS-1$
		type.createField(newfield, null, false, monitor);
	}

	private boolean isCross(String className) {
		String cross = UIManager.getCrossPlatformLookAndFeelClassName();
		return className == null || className.equals(cross);
	}

	private void removeRemovedComponent(WidgetAdapter root, IProgressMonitor monitor, ICompilationUnit unit, IType type) {
		List<String> removedNames = (List<String>) root.getProperty("removed.components");
		if (removedNames != null) {
			List<String> nonExistingFields = new ArrayList<String>();
			for (String name : removedNames) {
				IField field = type.getField(name);
				if (field == null || !field.exists())
					nonExistingFields.add(name);
			}
			for (String nonfield : nonExistingFields) {
				removedNames.remove(nonfield);
			}
			ASTParser astparser = ASTParser.newParser(AST.JLS3);
			astparser.setSource(unit);
			CompilationUnit cunit = (CompilationUnit) astparser.createAST(null);
			List<String> getmethods = new ArrayList<String>();
			for (int i = 0; i < removedNames.size(); i++) {
				String removed_name = removedNames.get(i);
				String getmethod = NamespaceUtil.getGetMethodName(cunit, removed_name);
				getmethods.add(getmethod);
			}
			for (int i = 0; i < removedNames.size(); i++) {
				String removed_name = removedNames.get(i);
				String getmethod = getmethods.get(i);
				removeField(type, removed_name, getmethod, monitor);
			}
		}
	}

	private void rename(IType type, WidgetAdapter root) {
		if (root.getLastName() != null && root.getName() != null && !root.getLastName().equals(root.getName())) {
			IParser parser = (IParser) root.getAdapter(IParser.class);
			if (parser != null) {
				parser.renameField(type, null);
			}
		}
		if (root.isRoot()) {
			for (InvisibleAdapter invisible : root.getInvisibles()) {
				renameInvisible(type, invisible);
			}
		}
		if (root instanceof CompositeAdapter) {
			CompositeAdapter container = (CompositeAdapter) root;
			int count = container.getChildCount();
			for (int i = 0; i < count; i++) {
				Component child = container.getChild(i);
				WidgetAdapter childAdapter = WidgetAdapter.getWidgetAdapter(child);
				rename(type, childAdapter);
			}
		}
	}

	private void renameInvisible(IType type, InvisibleAdapter root) {
		if (root.getLastName() != null && root.getName() != null && !root.getLastName().equals(root.getName())) {
			IParser parser = (IParser) root.getAdapter(IParser.class);
			if (parser != null) {
				parser.renameField(type, null);
			}
		}
	}

	private IWorkbenchPartSite getEditorSite() {
		IWorkbench workbench = PlatformUI.getWorkbench();
		if (workbench != null) {
			IWorkbenchWindow window = workbench.getActiveWorkbenchWindow();
			if (window != null) {
				IWorkbenchPage page = window.getActivePage();
				if (page != null) {
					IEditorPart editor = page.getActiveEditor();
					if (editor != null)
						return editor.getSite();
				}
			}
		}
		return null;
	}

	private void removeField(IType type, String fieldName, String methodName, IProgressMonitor monitor) {
		IField field = type.getField(fieldName);
		if (field != null && field.exists()) {
			try {
				field.delete(true, monitor);
			} catch (JavaModelException e) {
				ParserPlugin.getLogger().error(e);
				return;
			}
		}
		IMethod method = type.getMethod(methodName, new String[0]);
		if (method != null && method.exists()) {
			try {
				method.delete(true, monitor);
				return;
			} catch (JavaModelException e) {
				ParserPlugin.getLogger().error(e);
			}
		} else {
			for (IFieldParser parser : fieldParsers) {
				if (parser.removeField(type, fieldName, monitor))
					return;
			}
		}
	}
}
